/**
 * 
 */
package cn.com.dragonsoftbravo.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;

/**
 * Reusable input stream, extends from {@link InputStream}
 * 
 * @copyright Promoters Sell-thru System, ISSG, Sony China
 * 
 * @author William
 * 
 */
public class ReusableInputStream extends InputStream {

	private InputStream input;

	private ByteArrayOutputStream output;

	private ByteBuffer buffer;

	private boolean bufferReady = false;

	/**
	 * 
	 * @param input
	 * @throws IOException
	 */
	public ReusableInputStream(InputStream input) throws IOException {
		this.input = input;
		// Note: it's resizable anyway.
		this.output = new ByteArrayOutputStream(input.available());

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.InputStream#read()
	 */
	@Override
	public int read() throws IOException {
		byte[] b = new byte[1];
		read(b, 0, 1);
		return b[0];
	}

	@Override
	public int read(byte[] bytes) throws IOException {
		return read(bytes, 0, bytes.length);
	}

	@Override
	public int read(byte[] bytes, int offset, int length) throws IOException {

		// if (buffer == null || !bufferReady) {
		if (!bufferReady) {
			int read = input.read(bytes, offset, length);

			if (read <= 0) {
				input.close();
				input = null;

				// buffer = ByteBuffer.wrap(output.toByteArray());
				// MappedByteBuffer

				buffer = ByteBuffer.allocateDirect(output.toByteArray().length);
				buffer.put(output.toByteArray());
				this.bufferReady = true;
				buffer.flip();
				output = null;
				return read;
			} else {

				output.write(bytes, offset, read);
				return read;
			}
		} else {
			int read = Math.min(length, buffer.remaining());

			if (read <= 0) {
				buffer.flip();
				return -1;
			} else {
				buffer.get(bytes, offset, read);
				return read;
			}
		}

	}

	/**
	 * 
	 */
	public void close() throws IOException {

		IOCloseUtils.closeBatch(this.input, this.output);
		if (null != buffer) {
			buffer.clear();
			try {
				Method cleanerMethod = buffer.getClass().getMethod("cleaner");
				if (null != cleanerMethod) {
					cleanerMethod.setAccessible(true);
					Object cleaner = cleanerMethod.invoke(buffer);
					Method cleanMethod = cleaner.getClass().getMethod("clean");
					if (null != cleanerMethod) {
						cleanMethod.setAccessible(true);
						cleanMethod.invoke(cleaner);
					}
				}

			} catch (Exception e) {

			}
		}
		this.bufferReady = false;

	}

}
